﻿//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of NVIDIA CORPORATION nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2018 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.  

// ****************************************************************************
// This snippet illustrates simple use of physx
//
// It creates a number of box stacks on a plane, and if rendering, allows the
// user to create new stacks and fire a ball from the camera position
// ****************************************************************************

#include <ctype.h>

#include "PxPhysicsAPI.h"

#include "../SnippetCommon/SnippetPrint.h"
#include "../SnippetCommon/SnippetPVD.h"
#include "../SnippetUtils/SnippetUtils.h"
#define RENDER_SNIPPET 1
#include <vector>
using namespace physx;
using namespace std;

PxDefaultAllocator gAllocator;
PxDefaultErrorCallback gErrorCallback;
PxFoundation* gFoundation = NULL;
PxPhysics* gPhysics = NULL;
PxDefaultCpuDispatcher* gDispatcher = NULL;
PxScene* gScene = NULL;
PxPvd* gPvd = NULL;

// PxMaterial* gMaterial = NULL;

PxMaterial* ballMaterial = NULL;

PxMaterial* gMaterial = NULL;

PxMaterial* planeMaterial = NULL;

PxMaterial* bottleMaterial = NULL;

PxSimulationEventCallback* gSimCallBack;

std::vector<PxRigidDynamic*> gVector;
std::vector<PxRigidDynamic*> gBingoVector;
PxReal stackZ = 10.0f;
PxReal stackZCA = -90.0f;
PxReal stackXTB = 0.0f;

int ballNum = 3;
int abingo = 0;
int score = 0;
// bool bingoMap[10] = {false};
bool bingoMap[40] = { false };
int level = 1;
PxRigidDynamic* ball;

class myCollsionCallBack : public PxSimulationEventCallback {
	void onContact(const PxContactPairHeader& pairHeader,
		const PxContactPair* pairs, PxU32 nbPairs) {
		// printf("onContact\n");
	}
	void onTrigger(PxTriggerPair* pairs, PxU32 count) {}
	void onConstraintBreak(PxConstraintInfo*, PxU32) {}
	void onWake(PxActor**, PxU32) {}
	void onSleep(PxActor**, PxU32) {}
	void onAdvance(const PxRigidBody* const*, const PxTransform*, const PxU32) {
	}
};

void createDynamic(const PxTransform& camera) {
	if (ball != nullptr) {
		ball->release();
		ball = nullptr;
	}
	for (int i = 0; i < gBingoVector.size(); i++) {
		((PxRigidDynamic*)gBingoVector[i])->release();
	}
	gBingoVector.clear();

	PxVec3 velocity(0, 0, -14.0);

	PxTransform position(camera.p.x, 5.0 + 3.0, 100.0);
	PxShape* shape =
		gPhysics->createShape(PxSphereGeometry(4.0f), *ballMaterial, true);
	PxRigidDynamic* dynamic = gPhysics->createRigidDynamic(position);

	struct PxFilterData cap_data;

	cap_data.word0 = 0b01;
	cap_data.word1 = 0b10;
	shape->setSimulationFilterData(cap_data);

	dynamic->attachShape(*shape);
	dynamic->setAngularDamping(0.01f);
	dynamic->setLinearVelocity(velocity);
	PxRigidBodyExt::updateMassAndInertia(*dynamic, 1);
	gScene->addActor(*dynamic);
	ball = dynamic;
}

//创建球关节
PxJoint* createLimitedSpherical(PxRigidActor* a0, const PxTransform& t0,
	PxRigidActor* a1, const PxTransform& t1)

{
	PxSphericalJoint* j = PxSphericalJointCreate(*gPhysics, a0, t0, a1, t1);

	j->setLimitCone(PxJointLimitCone(-PxPi / 2, PxPi, 0.f));
	j->setSphericalJointFlag(PxSphericalJointFlag::eLIMIT_ENABLED, true);
	return j;

}

typedef PxJoint* (*JointCreateFunction)(PxRigidActor* a0, const PxTransform& t0,
	PxRigidActor* a1,
	const PxTransform& t1);

//创建摆锤
void createChain1(const PxTransform& t, PxU32 length, const PxGeometry& g,
	PxReal separation, JointCreateFunction createJoint)

{
	PxVec3 offset(separation / 2, 0, 0);
	PxTransform localTm(offset);
	PxRigidDynamic* prev = NULL;

	for (PxU32 i = 0; i < length; i++) {
		PxRigidDynamic* current =
			PxCreateDynamic(*gPhysics, t * localTm, g, *gMaterial, 5.0f);
		(*createJoint)(prev, prev ? PxTransform(offset) : t, current,
			PxTransform(-offset));

		//无重力
		current->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, true);
		//轴锁定
		current->setRigidDynamicLockFlags(
			PxRigidDynamicLockFlag::eLOCK_LINEAR_Z |
			PxRigidDynamicLockFlag::eLOCK_ANGULAR_X |
			PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y);
		/*if (i == 1) {*/

		current->setAngularVelocity(PxVec3(0.f, 0.f, 25.0f));
		//角速度
		current->setAngularDamping(0.f);

		gScene->addActor(*current);
		prev = current;
		localTm.p.x += separation;
	}
}
void createChain2(const PxTransform& t, PxU32 length, const PxGeometry& g,
	PxReal separation, JointCreateFunction createJoint) {
	PxVec3 offset(separation / 2, 0, 0);
	PxTransform localTm(offset);
	PxRigidDynamic* prev = NULL;

	for (PxU32 i = 0; i < length; i++) {
		PxRigidDynamic* current =
			PxCreateDynamic(*gPhysics, t * localTm, g, *gMaterial, 5.0f);
		(*createJoint)(prev, prev ? PxTransform(offset) : t, current,
			PxTransform(-offset));

		//无重力
		current->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, true);
		//轴锁定
		current->setRigidDynamicLockFlags(
			PxRigidDynamicLockFlag::eLOCK_LINEAR_Z |
			PxRigidDynamicLockFlag::eLOCK_ANGULAR_X |
			PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y);
		current->setAngularVelocity(PxVec3(0.f, 0.f, -10.0f));
		//角速度
		current->setAngularDamping(0.f);
		gScene->addActor(*current);
		prev = current;
		localTm.p.x += separation;
	}
}

// Wall
void createWallLeft() {
	PxShape* shape = gPhysics->createShape(PxBoxGeometry(2.0f, 8.0f, 100.0f),
		*planeMaterial);
	PxTransform position(
		PxVec3(PxReal(-60.0), PxReal(2), PxReal(0.0)));  // position
	PxRigidStatic* body = gPhysics->createRigidStatic(position);
	body->attachShape(*shape);
	gScene->addActor(*body);
	shape->release();
}

void createWallRight() {
	PxShape* shape = gPhysics->createShape(PxBoxGeometry(2.0f, 8.0f, 100.0f),
		*planeMaterial);
	PxTransform position(
		PxVec3(PxReal(50.0), PxReal(2), PxReal(0.0)));  // position
	PxRigidStatic* body = gPhysics->createRigidStatic(position);
	body->attachShape(*shape);
	gScene->addActor(*body);
	shape->release();
}

void createCapsules() {
	PxShape* shape = gPhysics->createShape(PxCapsuleGeometry(3.0, 3.0),
		*bottleMaterial, false);
	struct PxFilterData cap_data;
	cap_data.word0 = 0b10;
	cap_data.word1 = 0b11;
	shape->setSimulationFilterData(cap_data);
	PxTransform relativePose(PxQuat(PxHalfPi, PxVec3(0, 0, 1)));
	shape->setLocalPose(relativePose);

	for (int i = -3; i <= 0; i++) {
		float z = -97.0 + 6.0 * (4 + i);
		for (int j = 0; j < 1 - i; j++) {
			float x = 6.0 * (i + j * 2);
			const PxTransform position =
				PxTransform(PxVec3(x, 5.0 + 3.0 + 3.0, z));
			PxRigidDynamic* actor = gPhysics->createRigidDynamic(position);
			actor->attachShape(*shape);
			gVector.push_back(actor);
			PxRigidBodyExt::updateMassAndInertia(*actor, 1);
			gScene->addActor(*actor);
		}
	}
	shape->release();
}

//单个胶囊体
void createCapsulesBase(const PxTransform& t) {
	//直立
	PxTransform relativePose(PxQuat(PxHalfPi, PxVec3(0, 0, 1)));

	PxRigidDynamic* aCapsuleActor = gPhysics->createRigidDynamic(t);

	PxShape* aCapsuleShape = PxRigidActorExt::createExclusiveShape(
		*aCapsuleActor, PxCapsuleGeometry(3, 3), *ballMaterial);

	aCapsuleShape->setLocalPose(relativePose);
	PxRigidBodyExt::updateMassAndInertia(*aCapsuleActor, 1);

	gScene->addActor(*aCapsuleActor);
	gVector.push_back(aCapsuleActor);
	aCapsuleShape->release();
}

void createCapsulesBaseBig(const PxTransform& t) {
	PxTransform relativePose(PxQuat(PxHalfPi, PxVec3(0, 0, 1)));
	PxRigidDynamic* aCapsuleActor = gPhysics->createRigidDynamic(t);
	PxShape* aCapsuleShape = PxRigidActorExt::createExclusiveShape(
		*aCapsuleActor, PxCapsuleGeometry(3.5, 5), *ballMaterial);

	aCapsuleShape->setLocalPose(relativePose);
	PxRigidBodyExt::updateMassAndInertia(*aCapsuleActor, 1);
	gScene->addActor(*aCapsuleActor);
	gVector.push_back(aCapsuleActor);
	aCapsuleShape->release();
}

//第二关球瓶
void createCapsules2() {
	for (int i = 0; i < 2; i++) {
		for (size_t j = 0; j < 3; j++) {
			createCapsulesBase(
				PxTransform(PxVec3(stackXTB += 8.0f, 11.0f, stackZCA)));
		}
		stackZCA += 15.0f;
		stackXTB = 0.0f;
	}
	stackZCA = -90.0f;
	createCapsulesBase(PxTransform(PxVec3(stackXTB -= 5, 11.0f, stackZCA)));
	createCapsulesBase(
		PxTransform(PxVec3(stackXTB -= 8, 11.0f, stackZCA += 8)));
	createCapsulesBase(
		PxTransform(PxVec3(stackXTB += 1, 11.0f, stackZCA += 8)));
	createCapsulesBase(
		PxTransform(PxVec3(stackXTB += 5, 11.0f, stackZCA += 8)));
}

//第三关球瓶
void createCapsules3() {
	stackXTB = -20.0f;
	PxReal stackZCA = -90.0f;
	for (size_t j = 0; j < 6; j++) {
		createCapsulesBase(
			PxTransform(PxVec3(stackXTB += 8.0f, 11.0f, stackZCA)));
	}
	stackZCA += 13.0f;
	stackXTB = -8.0f;
	createCapsulesBaseBig(
		PxTransform(PxVec3(stackXTB += 8.0f, 11.0f, stackZCA)));
	createCapsulesBaseBig(
		PxTransform(PxVec3(stackXTB += 8.0f, 11.0f, stackZCA)));
	createCapsulesBaseBig(
		PxTransform(PxVec3(stackXTB += 8.0f, 11.0f, stackZCA)));
	stackZCA += 10.0f;
	stackXTB = -15.0f;

	for (size_t j = 0; j < 5; j++) {
		createCapsulesBase(
			PxTransform(PxVec3(stackXTB += 8.0f, 11.0f, stackZCA)));
	}
}

PxFilterFlags BallFilterShader(PxFilterObjectAttributes attributes0,
	PxFilterData filterData0,
	PxFilterObjectAttributes attributes1,
	PxFilterData filterData1, PxPairFlags& pairFlags,
	const void* constantBlock,
	PxU32 constantBlockSize) {
	// let triggers through
	if (PxFilterObjectIsTrigger(attributes0) ||
		PxFilterObjectIsTrigger(attributes1)) {
		pairFlags = PxPairFlag::eTRIGGER_DEFAULT;
		return PxFilterFlag::eDEFAULT;
	}
	// generate contacts for all that were not filtered above
	pairFlags = PxPairFlag::eCONTACT_DEFAULT;

	// trigger the contact callback for pairs (A,B) where
	// the filtermask of A contains the ID of B and vice versa.
	if ((filterData0.word0 & filterData1.word1) &&
		(filterData1.word0 & filterData0.word1))
		pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;

	return PxFilterFlag::eDEFAULT;
}

void initPhysics(bool /*interactive*/) {
	gFoundation =
		PxCreateFoundation(PX_FOUNDATION_VERSION, gAllocator, gErrorCallback);

	gPvd = PxCreatePvd(*gFoundation);
	PxPvdTransport* transport =
		PxDefaultPvdSocketTransportCreate("127.0.0.1", 5425, 10);
	gPvd->connect(*transport, PxPvdInstrumentationFlag::eALL);

	gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation,
		PxTolerancesScale(), true, gPvd);

	PxSceneDesc sceneDesc(gPhysics->getTolerancesScale());

	sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.0f);
	gDispatcher = PxDefaultCpuDispatcherCreate(2);
	sceneDesc.cpuDispatcher = gDispatcher;
	sceneDesc.filterShader = BallFilterShader;
	/*sceneDesc.simulationEventCallback = new myCollsionCallBack();*/
	gScene = gPhysics->createScene(sceneDesc);

	PxPvdSceneClient* pvdClient = gScene->getScenePvdClient();
	if (pvdClient) {
		pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true);
		pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONTACTS, true);
		pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES,
			true);
	}

	//球材质
	ballMaterial = gPhysics->createMaterial(0.1f, 0.1f, 0.8f);
	//摆锤
	gMaterial = gPhysics->createMaterial(0.0f, 0.0f, 0.3f);
	//地面
	planeMaterial = gPhysics->createMaterial(0.1, 0.1, 0.2);
	//球瓶
	bottleMaterial = gPhysics->createMaterial(0.3, 0.3, 0.8);

	PxRigidStatic* groundPlane =
		PxCreatePlane(*gPhysics, PxPlane(0, 1, 0, 0), *planeMaterial);
	gScene->addActor(*groundPlane);

	PxShape* shape =
		gPhysics->createShape(PxBoxGeometry(400.0, 2.5, 100.0), *planeMaterial);
	PxTransform position(
		PxVec3(PxReal(0.0), PxReal(2.5), PxReal(0.0)));  // position

	PxRigidStatic* body = gPhysics->createRigidStatic(position);

	body->attachShape(*shape);
	gScene->addActor(*body);

	//第一关球瓶
	createCapsules();
	createWallLeft();
	createWallRight();
}

void check_bingo() {
	if (!gVector.empty()) {
		int cbingo = 0;
		for (int i = 0; i < (int)gVector.size(); i++) {
			if (bingoMap[i] != true) {
				float y = ((PxRigidDynamic*)(gVector[i]))->getGlobalPose().p.y;
				if (y <= 9.0) {
					cbingo++;
					bingoMap[i] = true;
					gBingoVector.push_back(gVector[i]);
					gVector[i] = nullptr;
				}
			}
		}
		abingo += cbingo;
		score = abingo;
	}
}

void stepPhysics(bool interactive) {
	PX_UNUSED(interactive);
	gScene->simulate(1.0f / 60.0f);
	check_bingo();

	gScene->fetchResults(true);
}

void cleanupPhysics(bool interactive) {
	PX_UNUSED(interactive);
	gScene->release();
	gDispatcher->release();
	gPhysics->release();
	PxPvdTransport* transport = gPvd->getTransport();
	gPvd->release();
	transport->release();
	gFoundation->release();
	printf("BowlingGame done.\n");
}

void keyPress(unsigned char key, const PxTransform& camera) {
	switch (toupper(key)) {
	case 'R': {
		for (int i = 0; i < gVector.size(); i++) {
			if (gVector[i] != nullptr) {
				((PxRigidDynamic*)gVector[i])->release();
			}
		}
		for (int i = 0; i < gBingoVector.size(); i++) {
			((PxRigidDynamic*)gBingoVector[i])->release();
		}
		for (int i = 0; i < gVector.size(); i++) {
			bingoMap[i] = false;
		}
		gBingoVector.clear();
		gVector.clear();
		ballNum = 3;
		score = 0;
		abingo = 0;
		level = 1;
		createCapsules();
		break;
	}
	case ' ': {
		if (ballNum > 0) {
			// cout << ballNum-- << endl;
			ballNum--;
			createDynamic(camera);
		}
		break;
	}


	case 'N': {
		if (ball != nullptr) {
			ball->release();
			ball = nullptr;
		}
		for (int i = 0; i < gBingoVector.size(); i++) {
			((PxRigidDynamic*)gBingoVector[i])->release();
		}
		for (int i = 0; i < gVector.size(); i++) {
			if (gVector[i] != nullptr) {
				((PxRigidDynamic*)gVector[i])->release();
			}
		}
		for (int i = 0; i < gVector.size(); i++) {
			bingoMap[i] = false;
		}
		gBingoVector.clear();
		gVector.clear();
		level++;
		ballNum = 3;
		score = 0;
		abingo = 0;
		if (level == 2) {
			// ballNum = 256;
			createCapsules2();

			createChain1(PxTransform(PxVec3(0.0f, 31.0f, -30.0f)), 2,
				PxSphereGeometry(7.0f), 12.5f,
				createLimitedSpherical);
			createChain2(PxTransform(PxVec3(0.0f, 31.0f, -55.0f)), 2,
				PxSphereGeometry(7.0f), 12.5f,
				createLimitedSpherical);

		}

		else if (level == 3) {
			// ballNum = 256;
			createCapsules3();
		}
		else {
			level = 1;
			createCapsules();
		}
		break;
	}
	}
}

int snippetMain(int, const char* const*) {
#ifdef RENDER_SNIPPET
	extern void renderLoop();
	renderLoop();
#else
	static const PxU32 frameCount = 100;
	initPhysics(false);
	for (PxU32 i = 0; i < frameCount; i++) stepPhysics(false);
	cleanupPhysics(false);
#endif
	return 0;
}
